<template>
  <div class="table-wrap" :style="`background: ${bgColor}`">
    <div class="canvas-wrap">
      <canvas ref="canvasRef" :style="`width: ${tabWidth?tabWidth+'px':'100%'}`"></canvas>
      <div class="canvas-oper-header-mark" @mousedown="onHeaderMouseDown"></div>
      <div class="canvas-oper-mark" :style="`width: ${tabWidth?tabWidth+5+'px':'100%'}`" ref="canvasOperRef" @scroll="onScroll">
        <div
          class="canvas-scroll"
          :style="`height: ${tableData.length * rowHeight}px;width: ${tabWidth?tabWidth-5+'px':'100%'}`"
          @mousedown="onMousedown"
          @mousemove="onMousemove"
        ></div>
      </div>
    </div>
    <img src="./check.png" ref="checkIconRef">
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, reactive, toRefs, watch } from 'vue';
import { autoUnit, hexToRgb } from './index';
export default defineComponent({
  props: {
    data: Array,
    column: Array,
    config: Object
  },
  setup (props, { emit }) {
    const state: any = reactive({
      canvasRef: null,
      rowHeight: 30,
      tableData: [],
      tabWidth: 0,
      bgColor: '#fff',
      canvasOperRef: null,
      checkIconRef: null
    })
    const baseConfig = {
      colWidth: 100,                  // 列宽
      fontColor: '#000',              // 字体颜色
      fontSize: '12px',               // 字体大小和字体类型
      fontFamily: 'Arial',            // 字体大小和字体类型
      font: '12px Arial',
      bgColor: '#fff',                // 背景颜色
      thBgColor: '#fff',              // 标题背景颜色
      currentRowBgColor: '#ddd',      // 当前行的背景颜色
      currentRowFontColor: '',        // 当前行的字体颜色
      padding: 5,                     // 间距
      spaceText: '',                  // 单元格内容为空时自动默认内容
      exceedRrenderSize: 5,           // 超出范围渲染数量
      rowHeight: 30,                  // 行高
      border: false,                  // 表格边框
      borderColor: '#eee',            // 表格边框颜色
      borderWidth: 0,
      fixedSplitLineColor: '#409EFF', // 固定列时，分割线的颜色
      fixedBgColor: '',               // 固定列背景颜色
      checkBoxSize: 12,               // 多选框的大小
      progressColor: '#3498db',       // 进度条颜色
      hoverBgColor: '#eee',           // 鼠标滑过背景颜色
    };
    let refreshFlag = false;
    let config = JSON.parse(JSON.stringify(baseConfig));
    watch(computed(() => props.config), () => {
      if (props.config) {
        config = Object.assign(JSON.parse(JSON.stringify(baseConfig)), props.config);
        config.font = `${config.fontSize} ${config.fontFamily}`;
        config.borderWidth = config.border ? 1 : 0;
        state.bgColor = config.bgColor;
        state.rowHeight = config.rowHeight;
      }
    }, {immediate: true, deep: true})

    let canvas: any, ctx: any, deltaX = 0, deltaY = 0;
    onMounted(() => {
      canvas = state.canvasRef;
      ctx = canvas.getContext('2d');
      canvas.width = canvas.offsetWidth;
      canvas.height = canvas.offsetHeight;
      ctx.font = config.font;
      ctx.fillStyle = config.fontColor;
      renderTable();
    })
    
    const colWidth = props.config?.colWidth || 100;
    let column: any = [...(props.column as any)];
    column.sort((a: any, b: any) => {
      if ((a.fixed === true || a.fixed === 'left') && !(b.fixed === true || b.fixed === 'left')) return -1;
      if (!(a.fixed === true || a.fixed === 'left') && (b.fixed === true || b.fixed === 'left')) return 1;
      if (a.fixed === 'right' && b.fixed !== 'right') return 1;
      if (a.fixed !== 'right' && b.fixed === 'right') return -1;
      return 0;
    })
    let fixedLeftColumn: any = [];
    let fixedRightColumn: any = [];
    let fixedLeftOffsetX = 0, fixedRightOffsetX = 0;
    for (let i = 0; i < column.length; i++) {
      state.tabWidth += column[i].width || colWidth;
      if (column[i].fixed == 'right') {
        fixedRightOffsetX += (column[i].width || config.colWidth) + config.padding;
        fixedRightColumn.push(column[i]);
      } else if (column[i].fixed == 'left' || column[i].fixed === true) {
        fixedLeftOffsetX += (column[i].width || config.colWidth);
        fixedLeftColumn.push(column[i]);
      }
    }

    let oldData: any[] = [], selectionRows: any = {}, selectAllFlag = false, sortColumns: any = [];
    // 渲染多选框
    let checkBoxColumnOffsetX: any;
    let oldAniVal: any = {};
    watch(computed(() => props.data), () => {
      checkBoxColumnOffsetX = null;
      selectionRows = {};
      oldAniVal = {};
      oldData = state.tableData;
      state.tableData = props.data;
      if (state.canvasRef) {
        renderTable();
      }
    }, {immediate: true, deep: true})
    function renderTable() {
      refreshFlag = true;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      clearAniTimeout();
      renderTr();
      renderTheader();
      refreshFlag = false;
    }

    function renderTheader() {
      renderTh(column, deltaX, 0);
      if (fixedLeftColumn.length) {
        renderTh(fixedLeftColumn, 0);
      }
      if (fixedRightColumn.length) {
        renderTh(fixedRightColumn, canvas.width - fixedRightOffsetX + (fixedRightColumn.length - 1) * config.padding);
      }
      renderFixedSplitLine();
      if (config.border) {
        ctx.moveTo(0.5, 0);
        ctx.lineTo(canvas.width, 0);
        ctx.lineTo(canvas.width, canvas.height);
        ctx.lineTo(0.5, canvas.height);
        ctx.lineTo(0.5, 0);
        ctx.strokeStyle = config.borderColor;
        ctx.stroke();
      }
    }

    function renderFixedSplitLine() {
      if (!config.fixedSplitLineColor) return;
      if (fixedLeftColumn.length) {
        if (deltaX < 0) {
          ctx.fillStyle = config.fixedSplitLineColor;
        } else {
          ctx.fillStyle = config.borderColor;
        }
        ctx.fillRect(fixedLeftOffsetX + config.padding, 0, 1, canvas.height);
      }
      if (fixedRightColumn.length) {
        if (-deltaX < state.canvasOperRef.scrollWidth - state.canvasOperRef.offsetWidth) {
          ctx.fillStyle = config.fixedSplitLineColor;
        } else {
          ctx.fillStyle = config.borderColor;
        }
        ctx.fillRect(canvas.width - fixedRightOffsetX + (fixedRightColumn.length - 1) * config.padding, 0, 1, canvas.height);
      }
    }

    function renderTh(column: any[], offsetX: number, bgOffetX?: number) {
      if (config.thBgColor) {
        let startX = bgOffetX != undefined ? bgOffetX : offsetX;
        let columnAllWidth = 0
        for (let i = 0; i < column.length; i++) {
          columnAllWidth += column[i].width || config.colWidth;
        }
        ctx.fillStyle = config.thBgColor;
        ctx.fillRect(startX, 0, startX + columnAllWidth + config.padding, 40);
      }
      ctx.font = config.font;
      const fillStyle = config.fontColor;
      ctx.fillStyle = fillStyle;
      for (let i = 0; i < column.length; i++) {
        let val = column[i].name;
        let alignX = 0;
        // 绘制截断后的文字
        const truncatedText = truncateText(val, (column[i].width || colWidth) - config.padding * 2);
        if ((config.align || config.thAlign || column[i].align) == 'center') {
          alignX = (column[i].width || colWidth) / 2 - ctx.measureText(truncatedText).width / 2;
        } else if ((config.align || config.thAlign || column[i].align) == 'right') {
          alignX = (column[i].width || colWidth) - ctx.measureText(truncatedText).width - config.padding;
        }
        if (column[i].sort && column[i].type != 'index') {
          alignX -= 5
          renderSortIcon(i, config.padding + offsetX + alignX + ctx.measureText(truncatedText).width, fillStyle);
        }
        ctx.fillText(truncatedText, config.padding + offsetX + alignX, 30);
        if (config.border && i > 0) {
          ctx.fillStyle = config.borderColor;
          ctx.fillRect(offsetX + config.padding, 0, 0.5, 40);
          ctx.fillStyle = fillStyle;
        }
        if (column[i].color) {
          ctx.fillStyle = fillStyle;
        }
        offsetX += column[i]?.width || colWidth;
      }
    }

    function renderSortIcon(columnIndex: number, offsetX: number, fillStyle: string) {
      let findSortColumn = sortColumns.find((item: any) => item.prop == column[columnIndex].prop);
      if (findSortColumn?.direction == 'asc') {
        ctx.fillStyle = config.fixedSplitLineColor;
      }
      // 绘制上方三角形
      ctx.beginPath();
      ctx.moveTo(offsetX + 5, 20); // 三角形顶点
      ctx.lineTo(offsetX, 25); // 三角形底部左点
      ctx.lineTo(offsetX + 10, 25); // 三角形底部右点
      ctx.closePath();
      ctx.fill();
      if (findSortColumn?.direction == 'desc') {
        ctx.fillStyle = config.fixedSplitLineColor;
      } else {
        ctx.fillStyle = fillStyle;
      }
      // 绘制下方三角形
      ctx.beginPath();
      ctx.moveTo(offsetX + 5, 32); // 三角形顶点
      ctx.lineTo(offsetX, 27); // 三角形底部左点（与上方三角形共用）
      ctx.lineTo(offsetX + 10, 27); // 三角形底部右点（与上方三角形共用）
      ctx.closePath();
      ctx.fill();
      ctx.fillStyle = fillStyle;
    }

    function renderTr() {
      let begin = 0;
      if (deltaY < -config.exceedRrenderSize * config.rowHeight) {
        begin = Math.floor(-deltaY / config.rowHeight) - Math.floor(100 / config.rowHeight);
      }
      for (let i = begin; i < Math.max(canvas.height / config.rowHeight, state.tableData.length); i++) {
        if (!renderRow(state.tableData[i] || null, i)) return;
      }
    }

    let activeData: any = null;
    function renderRow(row: any, rowIndex: number, active?: boolean) {
      let y = 30 + deltaY + (rowIndex + 1) * config.rowHeight;
      if (!ctx || y < -100 || y > canvas.offsetHeight + 100) return false;
      let bgColor = config.bgColor;
      if (active) {
        if (activeData) {
          ctx.clearRect(config.borderWidth || 0, 30 + deltaY + (activeData.rowIndex + 1) * config.rowHeight - 20, canvas.width - config.borderWidth * 2, config.rowHeight);
          renderRow(activeData.row, activeData.rowIndex, false);
        }
        activeData = {
          row,
          rowIndex,
          y,
          deltaY
        };
        ctx.fillStyle = config.currentRowBgColor;
        ctx.fillRect(0, y - 20, canvas.width, config.rowHeight);
        bgColor = config.currentRowBgColor;
      } else if (active !== false && activeData && activeData.y - (activeData.deltaY - deltaY) == y) {
        ctx.fillStyle = config.currentRowBgColor;
        ctx.fillRect(0, y - 20, canvas.width, config.rowHeight);
        bgColor = config.currentRowBgColor;
      }
      ctx.font = config.font;
      const fillStyle = (active && config.currentRowFontColor) || config.fontColor;
      ctx.fillStyle = fillStyle;
      renderCol(column, row, rowIndex, deltaX, y, fillStyle);
      if (fixedLeftColumn.length) {
        renderCol(fixedLeftColumn, row, rowIndex, 0, y, fillStyle, config.fixedBgColor || bgColor);
      }
      if (fixedRightColumn.length) {
        renderCol(fixedRightColumn, row, rowIndex, canvas.width - fixedRightOffsetX + (fixedRightColumn.length - 1) * config.padding, y, fillStyle, config.fixedBgColor || bgColor);
      }
      if (!config.noBorder) {
        ctx.fillStyle = config.borderColor;
        ctx.fillRect(0, y - 20, canvas.width, 1);
        ctx.fillStyle = fillStyle;
      }
      return true;
    }

    function renderCol(column: any[], row: any, rowIndex: number, offsetX: number, y: number, fillStyle: string, bgColor?: string) {
      if (bgColor) {
        let startX = offsetX;
        let columnAllWidth = 0
        for (let i = 0; i < column.length; i++) {
          columnAllWidth += column[i].width || config.colWidth;
        }
        ctx.fillStyle = bgColor;
        ctx.fillRect(startX + config.borderWidth, y - 20, startX + columnAllWidth + config.padding - config.borderWidth, config.rowHeight);
        renderFixedSplitLine();
      }
      for (let i = 0; i < column.length; i++) {
        if (offsetX > canvas.offsetWidth + config.colWidth) break;
        if (config.border && i > 0) {
          ctx.fillStyle = config.borderColor;
          ctx.fillRect(offsetX + config.padding, y - 20, 0.5, config.rowHeight);
          ctx.fillStyle = fillStyle;
        }
        if (!(offsetX < deltaX - config.colWidth || row == null)) {
          let val = row[column[i].prop];
          if (val) {
            if (typeof column[i].divisor == 'number') {
              val /= column[i].divisor;
            }
            if (column[i].toFixed) {
              val = val.toFixed(column[i].toFixed);
            } 
            // 自动添加单位
            if (column[i].autoUnit) {
              val = autoUnit(val, column[i].toFixed);
            }
            // 自动千分位
            else if (column[i].thousandth) {
              val = val.toLocaleString();
            } else if (column[i].unit) {
              val += column[i].unit;
            }
          } else {
            val = config.spaceText;
          }
          if (column[i].render instanceof Function) {
            val = column[i].render(row);
          }
          if (column[i].type == 'index') {
            val = rowIndex + 1;
          }
          if (column[i].type == 'selection') {
            renderCheckbox(ctx, row, rowIndex, i, offsetX, y);
          } else if (column[i].type == 'trends') {
            renderTrends(ctx, val, offsetX, y);
          } else if (column[i].type == 'progress' && !isNaN(Number(val))) {
            renderProgress(ctx, row, rowIndex, i, offsetX, y);
          } else {
            let _fillStyle = fillStyle;
            let alignX = 0;
            // 绘制截断后的文字
            const truncatedText = truncateText(val, (column[i].width || colWidth) - config.padding * 2);
            if (column[i].color instanceof String) {
              _fillStyle = column[i].color;
            } else if (column[i].color instanceof Function) {
              _fillStyle = column[i].color(row, rowIndex) || fillStyle;
            }
            let bgColor;
            if (column[i].bgColor instanceof String) {
              bgColor = column[i].bgColor;
            } else if (column[i].bgColor instanceof Function) {
              bgColor = column[i].bgColor(row, rowIndex);
            }
            if (bgColor) {
              ctx.fillStyle = bgColor;
              ctx.fillRect(offsetX + config.padding, y - 20, (column[i].width || colWidth), config.rowHeight);
            }
            if ((config.align || column[i].align) == 'center') {
              alignX = (column[i].width || colWidth) / 2 - ctx.measureText(truncatedText).width / 2;
            } else if ((config.align || column[i].align) == 'right') {
              alignX = (column[i].width || colWidth) - ctx.measureText(truncatedText).width - config.padding;
            }
            if (refreshFlag) {
              handleAni(i, row, val, offsetX, rowIndex, truncatedText, alignX, _fillStyle, fillStyle);
            }
            ctx.fillStyle = _fillStyle;
            ctx.fillText(truncatedText, config.padding + offsetX + alignX + config.borderWidth, y + config.borderWidth);
            
            if (column[i].color) {
              ctx.fillStyle = fillStyle;
            }
          }
        }
        offsetX += column[i]?.width || colWidth;
      }
    }

    function handleAni(i: any, row: any, val: any, offsetX: any, rowIndex: any, truncatedText: any, alignX: any, _fillStyle: any, fillStyle: any) {
      if (config.rowKey && column[i].dynamicHighlighting) {
        let findItem;
        let findVal = oldAniVal[row[config.rowKey] + column[i].prop];
        if (!findVal) {
          findItem = findRowOnOldData(rowIndex, row[config.rowKey]);
          if (findItem) {
            oldAniVal[row[config.rowKey] + column[i].prop] = findItem[column[i].prop];
            findVal = oldAniVal[row[config.rowKey] + column[i].prop];
          }
        } else {
          findItem = {
            [column[i].prop]: findVal
          }
        }
        
        if (findItem && (findItem[column[i].prop] < val || findItem[column[i].prop] > val)) {
          let y = 30 + deltaY + (rowIndex + 1) * config.rowHeight - 20;
          
          // 检查是否是固定列
          const isFixedColumn = column[i].fixed === true || column[i].fixed === 'left' || column[i].fixed === 'right';
          if (!isFixedColumn) {
            let aniPos = {
              x: offsetX + config.padding + config.borderWidth,
              w: column[i].width || colWidth - config.borderWidth * 2,
              h: config.rowHeight - config.borderWidth * 2,
            }
            let alpha = 0.5;
            let bgColor = findItem[column[i].prop] < val ? `rgba(255, 59, 61, ` : `rgba(107, 249, 91, `;
            
            // 保存当前上下文状态
            ctx.save();
            
            // 设置裁剪区域，排除固定列区域
            ctx.beginPath();
            ctx.rect(
              fixedLeftOffsetX + config.padding, 
              0, 
              canvas.width - fixedLeftOffsetX - fixedRightOffsetX, 
              canvas.height
            );
            ctx.clip();
            
            // 绘制动画效果
            ctx.fillStyle = bgColor + alpha + ')';
            ctx.fillRect(aniPos.x, y + config.borderWidth, aniPos.w, aniPos.h);
            
            // 恢复上下文状态
            ctx.restore();
            
            animate(rowIndex, alpha - 0.02, bgColor, aniPos, _fillStyle, fillStyle, truncatedText, alignX, 500);
          }
        }
      }
    }

    function findRowOnOldData(rowIndex: number, rowKey: string, offset = 100, count = 0): any {
      if (!oldData.length) return;
      let start = rowIndex + count * offset;
      start = start > oldData.length - 1 ? oldData.length - 1 : start;
      let end = rowIndex + (count + 1) * offset;
      end = end > oldData.length - 1 ? oldData.length - 1 : end;
      for (let index = start; index < end; index++) {
        const item = oldData[index];
        if (item[config.rowKey] == rowKey) {
          return item;
        }
      }
      let start2 = rowIndex - (count + 1) * offset;
      start2 = start2 < 0 ? 0 : start2;
      let end2 = rowIndex - count * offset;
      end2 = end2 < 0 ? 0 : end2;
      for (let index = start2; index < end2; index++) {
        const item = oldData[index];
        if (item && item[config.rowKey] == rowKey) {
          return item;
        }
      }
      if (start <= 0 || end2 >= oldData.length - 1) {
        return null;
      }
      return findRowOnOldData(rowIndex, rowKey, offset, count + 1);
    }

    let animateTimer: any = {};
    function animate(rowIndex: any, alpha: any, bgColor: any, aniPos: any, _fillStyle: any, fillStyle: any, truncatedText: any, alignX: any, time: any) {
      let timer: any = setTimeout(() => {
        delete animateTimer[timer];
        let y = 30 + deltaY + (rowIndex + 1) * config.rowHeight - 20 + config.borderWidth;
        
        // 保存上下文状态
        ctx.save();
        
        // 设置裁剪区域，排除表头区域
        ctx.beginPath();
        ctx.rect(
          fixedLeftOffsetX + config.padding, 
          40, // 从表头下方开始裁剪
          canvas.width - fixedLeftOffsetX - fixedRightOffsetX - config.padding * 2, 
          canvas.height - 40 // 减去表头高度
        );
        ctx.clip();
        
        ctx.clearRect(aniPos.x, y, aniPos.w, aniPos.h);
        
        // 绘制动画效果
        if (activeData?.rowIndex == rowIndex) {
          ctx.fillStyle = config.currentRowBgColor;
          ctx.fillRect(aniPos.x, y, aniPos.w, aniPos.h);
        }
        ctx.fillStyle = bgColor + alpha + ')';
        ctx.fillRect(aniPos.x, y, aniPos.w, aniPos.h);
        ctx.fillStyle = _fillStyle;
        ctx.fillText(truncatedText, aniPos.x + alignX, y + 20);
        
        // 恢复上下文状态
        ctx.restore();
        
        // 重绘固定列分割线
        renderFixedSplitLine();
        
        if (alpha - 0.02 > 0) {
          animate(rowIndex, alpha - 0.02, bgColor, aniPos, _fillStyle, fillStyle, truncatedText, alignX, 10);
        } else {
          let timer: any = setTimeout(() => {
            delete animateTimer[timer];
            
            ctx.save();
            ctx.beginPath();
            ctx.rect(
              fixedLeftOffsetX + config.padding, 
              40, // 从表头下方开始裁剪
              canvas.width - fixedLeftOffsetX - fixedRightOffsetX - config.padding * 2, 
              canvas.height - 40 // 减去表头高度
            );
            ctx.clip();
            
            ctx.clearRect(aniPos.x, y, aniPos.w, aniPos.h);
            if (activeData?.rowIndex == rowIndex) {
              ctx.fillStyle = config.currentRowBgColor;
              ctx.fillRect(aniPos.x, y, aniPos.w, aniPos.h);
            }
            ctx.fillStyle = _fillStyle;
            ctx.fillText(truncatedText, aniPos.x + alignX, y + 20);
            
            ctx.restore();
            
            // 重绘固定列分割线
            renderFixedSplitLine();
          }, 10)
          animateTimer[timer] = true;
        }
      }, time)
      animateTimer[timer] = true;
    }

    function clearAniTimeout() {
      for (const key in animateTimer) {
        clearTimeout((key as any));
        delete animateTimer[key];
      }
    }

    function renderCheckbox(ctx: any, row: any, rowIndex: number, columnIndex: number, offsetX: any, y?: any) {
      if (y == undefined) {
        y =  30 + deltaY + (rowIndex + 1) * config.rowHeight;
      }
      checkBoxColumnOffsetX = [offsetX, offsetX + (column[columnIndex].width || colWidth) / 2, y - config.rowHeight / 2 + 3, columnIndex];
      
      const isChecked = selectAllFlag || selectionRows[row[config.rowKey]];
      
      if (isChecked) {
        ctx.drawImage(state.checkIconRef, offsetX + (column[columnIndex].width || colWidth) / 2, y - config.rowHeight / 2 + 3, config.checkBoxSize, config.checkBoxSize);
      } else {
        ctx.strokeStyle = config.borderColor;
        ctx.strokeRect(offsetX + (column[columnIndex].width || colWidth) / 2, y - config.rowHeight / 2 + 3, config.checkBoxSize, config.checkBoxSize);
      }
    }

    // 渲染折线图
    function renderTrends(ctx: any, data: any, offsetX: any, y: any) {
      // 设置线条颜色和宽度
      ctx.strokeStyle = '#3498db';
      ctx.lineWidth = 2;

      let min = data[0], max = data[0];
      for (let i = 0; i < data.length; i++) {
        if (min > data[i]) min = data[i];
        if (max < data[i]) max = data[i];
      }
      const rangeHeight = (config.rowHeight - config.padding * 2) / (max - min);
      const baseY = y + 5;
      // 绘制折线图
      ctx.beginPath();
      // 移动到第一个点
      ctx.moveTo(config.padding * 2 + offsetX, baseY - ((data[0] - min) * rangeHeight)); // 假设最大值占高度的3.5倍

      // 绘制到其他点
      data.forEach((_y: any, i: any) => {
        ctx.lineTo(config.padding * 2 + offsetX + i * (colWidth - config.padding * 2) / data.length, baseY - ((_y - min) * rangeHeight));
      });
      // 描边路径
      ctx.stroke();
    }

    // 渲染进度条
    function renderProgress(ctx: any, row: any, rowIndex: number, columnIndex: number, offsetX: any, y: any) {
      let progressColor = config.progressColor;
      if (typeof column[columnIndex].progressColor == 'string') {
        progressColor = column[columnIndex].progressColor;
      } else if (column[columnIndex].progressColor instanceof Function) {
        progressColor = column[columnIndex].progressColor(row, rowIndex) || progressColor;
      }
      let {r, g, b} = hexToRgb(progressColor);
      let val = row[column[columnIndex].prop];
      ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${0.7})`;
      const w = (column[columnIndex].width || config.colWidth) - config.padding * 2;
      ctx.fillRect(config.padding * 2 + offsetX, y - 20 + config.padding, w, config.rowHeight - config.padding * 2);
      ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
      ctx.fillRect(config.padding * 2 + offsetX, y - 20 + config.padding, Math.min(val / 100 * w, w), config.rowHeight - config.padding * 2);
      ctx.fillStyle = '#fff';
      ctx.fillText(val.toFixed(2) + '%', config.padding * 2 + offsetX, y);
    }

    // 截断文字并添加省略号
    function truncateText(text: string, maxWidth: number) {
      let truncated = text + '', flag = false;
      if (ctx.measureText(truncated).width > maxWidth && truncated.length > 0) {
        while (ctx.measureText(truncated + '...').width > maxWidth && truncated.length > 0) {
          truncated = truncated.substring(0, truncated.length - 1);
          flag = true;
        }
      }
      return flag ? truncated + '...' : text;
    }

    function onMousewheel(e: any) {
      if ((deltaY == 0 && e.deltaY < 0) || (deltaY == -(state.tableData.length * 30 - canvas.offsetHeight) && e.deltaY > 0)) return;
      deltaY -= e.deltaY;
      if (deltaY > 0) {
        deltaY = 0;
      }
      if (deltaY < -(state.tableData.length * config.rowHeight - canvas.offsetHeight)) {
        deltaY = -(state.tableData.length * config.rowHeight - canvas.offsetHeight);
      }
      renderTable();
    }

    function onMousedown(e: any) {
      let index = Math.floor(e.offsetY / config.rowHeight);
      if (index > -1) {
        const item = state.tableData[index];
        if (
          checkBoxColumnOffsetX
          && checkBoxColumnOffsetX[1] <= e.offsetX
          && checkBoxColumnOffsetX[1] + config.checkBoxSize >= e.offsetX
          && e.offsetY % config.rowHeight >= config.rowHeight / 2 - config.checkBoxSize / 2
          && e.offsetY % config.rowHeight <= config.rowHeight / 2 + config.checkBoxSize / 2
        ) {
          if (selectAllFlag) {
            selectAllFlag = false;
            delete selectionRows[item[config.rowKey]];
          } else {
            if (selectionRows[item[config.rowKey]]) {
              delete selectionRows[item[config.rowKey]];
            } else {
              selectionRows[item[config.rowKey]] = item;
            }
          }
          renderTable();
          emitSelectionChange();
          return;
        }
        renderRow(item, index, true);
        emit('row-click', item || null, index);
      }
    }

    let hoverIndex = -1;
    function onMousemove(e: any) {
      // let index = Math.floor(e.offsetY / config.rowHeight);
      // if (index > -1) {
      //   hoverIndex = index;
      //   const item = state.tableData[index];
      //   renderRow(item, index, true);
      // }
    }

    function onHeaderMouseDown(e: any) {
      const { item } = getColumnOfX(e.offsetX);
      if (item) {
        if (item.type == 'selection' && item.selectAll) {
          selectAllFlag = !selectAllFlag;
          if (selectAllFlag) {
            state.tableData.forEach((row: any) => {
              selectionRows[row[config.rowKey]] = row;
            });
          } else {
            selectionRows = {};
          }
          renderTable();
          emitSelectionChange();
        } else if (item.sort) {
          sortTable(item.prop);
        }
      }
    }

    // 排序函数
    function multisort(a: any, b: any, columns: any) {
      for (let i = 0; i < columns.length; i++) {
        const column = columns[i].prop;
        const direction = columns[i].direction;
        let valA = a[column];
        let valB = b[column];

        if (direction == 'asc') {
          // 如果是数字，转换为数字进行比较
          if (!isNaN(Number(valA))) valA = Number(valA);
          if (!isNaN(Number(valB))) valB = Number(valB);
          if (valA < valB) return -1;
          if (valA > valB) return 1;
          let r = valA - valB;
          if (isNaN(r) && valA != valB && valA['localeCompare']) {
            return valA['localeCompare'](valB);
          }
        } else if (direction == 'desc') {
          // 如果是数字，转换为数字进行比较
          if (!isNaN(Number(valA))) valA = Number(valA);
          if (!isNaN(Number(valB))) valB = Number(valB);
          if (valA > valB) return -1;
          if (valA < valB) return 1;
          let r = valA - valB;
          if (isNaN(r) && valA != valB && valB['localeCompare']) {
            return valB['localeCompare'](valA);
          }
        }
      }
      return 0; // 如果所有比较的列都相等，则保持原始顺序
    }

    function sortTable(prop: string) {
      let findSortColumn = sortColumns.find((item: any) => item.prop == prop);
      if (!findSortColumn) {
        sortColumns.push({
          prop,
          direction: 'asc'
        })
      } else if (findSortColumn.direction == 'asc') {
        findSortColumn.direction = 'desc';
      } else {
        sortColumns = sortColumns.filter((item: any) => item.prop != prop);
      }
      if (config.sortFn instanceof Function) {
        config.sortFn(sortColumns);
      } else {
        if (sortColumns.length) {
          let tableData = JSON.parse(JSON.stringify(props.data));
          tableData.sort((a: any, b: any) => multisort(a, b, sortColumns));
          state.tableData = tableData;
        } else {
          state.tableData = props.data;
        }
      }
      renderTable();
    }

    function getColumnOfX(x: number): any {
      let columnIndex, offsetX = 0;
      if (fixedLeftColumn.length) {
        for (let i = 0; i < fixedLeftColumn.length; i++) {
          const item = fixedLeftColumn[i];
          if (offsetX + (item.width || config.colWidth) + config.padding >= x) {
            columnIndex = i;
            break;
          }
          offsetX+=(item.width || config.colWidth);
        }
      }
      if (columnIndex == undefined && fixedRightColumn.length) {
        offsetX = canvas.width - fixedRightOffsetX + (fixedRightColumn.length - 1) * config.padding;
        for (let i = 0; i < fixedRightColumn.length; i++) {
          const item = fixedRightColumn[i];
          if (x >= offsetX && offsetX + (item.width || config.colWidth) + config.padding >= x) {
            columnIndex = column.length - fixedRightColumn.length + i;
            break;
          }
          offsetX+=(item.width || config.colWidth);
        }
      }
      if (columnIndex == undefined) {
        offsetX = 0;
        for (let i = 0; i < column.length; i++) {
          const item = column[i];
          if (offsetX + (item.width || config.colWidth) + config.padding >= x - deltaX) {
            columnIndex = i;
            break;
          }
          offsetX+=(item.width || config.colWidth);
        }
      }
      if (columnIndex != undefined) {
        return {item: column[columnIndex], index: columnIndex};
      }
      return {};
    }

    function onScroll(e: any) {
      deltaY = -e.target.scrollTop;
      deltaX = -e.target.scrollLeft;
      requestAnimationFrame(() => {
        let _oldData = oldData, _oldAniVal = oldAniVal;
        oldData = [];
        oldAniVal = {};
        renderTable();
        oldData = _oldData;
        oldAniVal = _oldAniVal;
      });
    }
    
    function resize() {
      canvas.width = canvas.offsetWidth;
      canvas.height = canvas.offsetHeight;
      renderTable();
    }

    // 在选择状态改变时触发事件
    function emitSelectionChange() {
      const selectedRows = Object.values(selectionRows);
      emit('selection-change', selectedRows);
    }

    // 暴露给外部的获取选中行的方法
    function getSelection() {
      return Object.values(selectionRows);
    }

    return {
      ...toRefs(state),
      onMousewheel,
      onMousedown,
      onMousemove,
      onHeaderMouseDown,
      onScroll,
      resize,
      emitSelectionChange,
      getSelection
    }
  }
})
</script>

<style lang='scss' scoped>
.table-wrap {
  height: 100%;
  width: 100%;
  overflow: hidden;
}
.canvas-wrap {
  position: relative;
  height: 100%;
  width: 100%;
  canvas {
    height: 100%;
    width: 100%;
    max-width: 100%;
  }
  .canvas-oper-header-mark {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 40px;
  }
  .canvas-oper-mark {
    position: absolute;
    left: 0;
    top: 40px;
    width: 100%;
    max-width: 100%;
    height: calc(100% - 40px);
    overflow: auto;
    &::-webkit-scrollbar {
      width: 10px;               
      height: 10px;
    }
    /*滚动条里面小方块*/
    &::-webkit-scrollbar-thumb {
      border-radius: 10px;
      background: #666;
      background-clip: padding-box;
      border: 4px solid transparent;
      &:hover {
        border: 2px solid transparent;
      }
    }
    /*滚动条里面轨道*/
    &::-webkit-scrollbar-track {
      border-radius: 10px;
      background: transparent;
      &:hover {
        background-color: #222;
      }
    }
    &::-webkit-scrollbar-corner {
      background-color: transparent;
    }
  }
}
</style>